//******************************************************************
//******************************************************************
//**********          ANts Peer To Peer Sources        *************
//
// ANts P2P realizes a third generation P2P net. It protects your
// privacy while you are connected and makes you not trackable, hiding
// your identity (ip) and crypting everything you are sending/receiving
// from others.

// Copyright (C) 2004  Roberto Rossi

// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

package ants.p2p.security.sockets;

import kerjodando.vincent.Common;
import java.io.*;
import java.security.*;
import java.security.spec.*;
import javax.crypto.*;
import javax.crypto.spec.*;

import ants.p2p.*;

import org.apache.log4j.*;

public class SecureClientSocketThread {
	public static final String cipher = "AES";
	public static final int cipherKeySize = 16;
	public static final int DHKeyExchangeBits = 512;
	public static final int MinDHKeyExchangeBits = 512;

	static Logger _logger = Logger.getLogger(SecureClientSocketThread.class
			.getName());

	DHParameterSpec dhParamSpec;
	KeyPair clientKpair;
	KeyAgreement clientKeyAgree;
	PublicKey serverPubKey;
	PublicKey clientPubKey;
	String sURI = null;

	byte[] sharedSecret;
	byte[] aesKey;
	Cipher clientCipherEnc;
	Cipher clientCipherDec;

	long timeElapsed;
	int pingSize = 32;

	boolean newVersionDetected = false;
	String newerVersion = Ant.getVersion();

	SecureClientSocket localSocket;
	InputStream socketInputStream;
	OutputStream socketOutputStream;

	public SecureClientSocketThread(SecureClientSocket localSocket)
			throws IOException, NoSuchAlgorithmException,
			InvalidParameterSpecException, InvalidAlgorithmParameterException,
			InvalidKeyException {
		this.localSocket = localSocket;
	}

	public void run() {
		try {
			_logger.info("Client is Accepting...");
			this.clientCipherEnc = Cipher
					.getInstance(SecureClientSocketThread.cipher);
			this.clientCipherDec = Cipher
					.getInstance(SecureClientSocketThread.cipher);

			this.socketInputStream = localSocket.getInputStream();
			this.socketOutputStream = localSocket.getOutputStream();

			// output my information
			ObjectSocketOutputStream oos = new ObjectSocketOutputStream(
					this.socketOutputStream);
			oos.writeObject(Ant.getProtocolVersion());
			oos.writeObject(Ant.getApplicationName());
			oos.writeObject(Ant.getVersion());
			oos.writeInt(this.localSocket.getLocalServerPort());
			oos.flush();

			// verify other peer's information
			ObjectSocketInputStream osi = new ObjectSocketInputStream(
					this.socketInputStream);
			String protocolVersion = (String) osi.readObject();
			if (!protocolVersion.equals(Ant.getProtocolVersion())) {
				if (!protocolVersion.startsWith("beta")
						&& protocolVersion.compareTo(Ant.getProtocolVersion()) > 0) {
					this.newVersionDetected = true;
					this.newerVersion = "Protocol update: " + protocolVersion;
				}
				this.localSocket.close();
				return;
			}
			String applicationName = (String) osi.readObject();
			String version = (String) osi.readObject();
			if (applicationName.equals(Ant.getApplicationName())) {
				if (!version.equals(Ant.getVersion())) {
					if (version.compareTo(Ant.getVersion()) > 0) {
						this.newVersionDetected = true;
						this.newerVersion = version;
					}
				}
			}

			long startProbe = System.currentTimeMillis();
			oos.writeObject(new byte[pingSize]);
			oos.writeObject(Common.identifier_id);
			oos.flush();
			osi.readObject();
			timeElapsed = (System.currentTimeMillis() - startProbe) / 2;
			this.sURI = (String) osi.readObject();

			/*
			 * this.skip1024Base = new
			 * BigInteger((String)keyAgreementInStream.readObject());
			 * this.skip1024Modulus = new
			 * BigInteger((String)keyAgreementInStream.readObject());
			 * this.generateDHParameters(); BigInteger y = new
			 * BigInteger((String)keyAgreementInStream.readObject());
			 * DHPublicKeySpec serverPubKeySpec = new DHPublicKeySpec(y,
			 * this.dhParamSpec.getP(),this.dhParamSpec.getG());
			 * this.serverPubKey =
			 * KeyFactory.getInstance("DH").generatePublic(serverPubKeySpec);
			 * keyAgreementOutStream.writeObject(((DHPublicKey)clientPubKey).getY().toString());
			 * keyAgreementOutStream.flush();
			 * 
			 * clientKeyAgree.doPhase(serverPubKey, true);
			 * 
			 * this.sharedSecret = clientKeyAgree.generateSecret();
			 * 
			 * this.aesKey = new
			 * byte[SecureClientSocketThread.cipherKeySize];//AES 16 for (int x =
			 * 0; x < this.aesKey.length; x++) { this.aesKey[x] =
			 * this.sharedSecret[x]; } SecretKeySpec clientKeySpec = new
			 * SecretKeySpec(aesKey, SecureClientSocketThread.cipher);
			 * this.clientCipherEnc.init(Cipher.ENCRYPT_MODE, clientKeySpec);
			 * this.clientCipherDec.init(Cipher.DECRYPT_MODE, clientKeySpec);
			 */

			_logger.info("Neighbourg accepted.");
		} catch (Exception e) {
			try {
				this.localSocket.close();
			} catch (Exception ex) {
			}
			_logger.info("Failed secure connection with "
					+ localSocket.getInetAddress() + ":"
					+ localSocket.getPort() + " :" + e.getMessage(), e);
			_logger.debug("Failed secure connection ", e);
		}
	}

	public String getURI() {
		return this.sURI;
	}

	public boolean isNewVersionDetected() {
		return this.newVersionDetected;
	}

	public String getNewerVersion() {
		return this.newerVersion;
	}

	public long getTimeElapsed() {
		return this.timeElapsed;
	}

	public Cipher getCipherEnc() {
		return this.clientCipherEnc;
	}

	public Cipher getCipherDec() {
		return this.clientCipherDec;
	}

	public SecureClientSocket getSocket() {
		return this.localSocket;
	}

}
